<?php

namespace app\api\service;

use app\model\User;
use app\common\Common;
use app\common\Error;
use think\facade\Cache;
use app\model\UserLoginRecord;
use app\model\Link;
use app\model\Applet;
use app\model\Money;
use app\model\PayRecord;
use think\facade\Db;
use think\facade\Queue;

class TiktokService
{
    private $model;
    private $userLoginRecordModel;
    private $linkModel;
    private $appletModel;
    private $moneyModel;
    private $payRecordModel;

    public function __construct(User $model, UserLoginRecord $userLoginRecordModel, Link $linkModel, Applet $appletModel, Money $moneyModel, PayRecord $payRecordModel)
    {
        $this->model = $model;
        $this->userLoginRecordModel = $userLoginRecordModel;
        $this->linkModel = $linkModel;
        $this->appletModel = $appletModel;
        $this->moneyModel = $moneyModel;
        $this->payRecordModel = $payRecordModel;
    }

    // 抖音登录
    public function tiktokUserLogin($params)
    {
        $appId = $params['app_id'];
        $appletData = $this->appletModel->getInfoByWhere(['app_id' => $appId], 'id, app_secret');

        if (empty($appletData)) {
            return ['message' => Error::APPID_PARAMATER_ERROR];
        }
        $secret = $appletData['app_secret'];

        $code = $params['code'];

        $url = 'https://developer.toutiao.com/api/apps/v2/jscode2session';

        $requestData = [
            'appid' => $appId,
            'secret' => $secret,
            'code' => $code
        ];

        $result = Common::curlRequest($url, 'POST', json_encode($requestData), ['Content-Type: application/json']);
        $result = json_decode($result, true);

        if (empty($result) || $result['err_no']) {
            return ['message' => Error::FAIL_AUTHORIZED_lOGIN];
        }

        $openId = $result['data']['openid'];
        $userInfo = $this->model->getInfoByOpenId($openId, 'id, user_name, avatar');

        $officialAccountId = '';
        $saveData = [
            'platform' => 2,
            'open_id' => $openId,
            'last_login_time' => time(),
            'updated_at' => time(),
            'source' => 4, // by add huangxiaoshan 2023-07-18
            'mobile_system' => $params['mobile_system'] ?? '', // by add huangxiaoshan 2023-07-18
        ];
        //获取未从链接id进来的用户对应的公众号id
        $officialAccountData = Db::name('official_accounts')->where(['applet_id' => $appletData['id']])->field('id')->limit(1)->select()->toArray();
        if(!empty($officialAccountData)){
            $saveData['official_account_id'] = $officialAccountData[0]['id'];//公众号id
        }
        if (!empty($params['link_id'])) {
            $saveData['link_id'] = $params['link_id'];
            $saveData['activation_time'] = date('Y-m-d H:i:s');
            $saveData['source'] = 1; // add by huangxiaoshan 2023-07-18
            $saveData['clickid'] = $params['clickid'] ?? ''; // update by huangxiaoshan 2023-07-18
            $linkData = $this->linkModel->getInfoByWhere(['id' => $params['link_id']], 'official_account_id');
            $officialAccountId = $linkData['official_account_id'];

            $saveData['official_account_id'] = $officialAccountId;

            // update by huangxiaoshan 2023-07-20
            if (!empty($params['promotionid'])) {
                $saveData['advertiser_id'] = $params['promotionid'];
            } else if (!empty($params['adid'])) {
                $saveData['advertiser_id'] = $params['adid'];
            }
            // end.
        }

        if ($userInfo) {
            $saveData['id'] = $userInfo['id'];
            $saveData['applet_id'] = $appletData['id'];
        } else {
            $saveData['applet_id'] = $appletData['id'];
            $saveData['register_date'] = intval(date('Ymd'));
            $saveData['created_at'] = time();
        }

        $uid = $this->model->edit($saveData);
        if ($uid == 13096621 || $uid == 16238470) {
            file_put_contents('./public_login.txt', date('Y-m-d H:i:s').' ：'.json_encode($params).'；'.PHP_EOL, FILE_APPEND);
        }

        $token = Common::createToken($uid);

        $redis = Cache::store('redis')->handler();
        $key = config('redis_key.api.user_login');
        $linkIdKey = config('redis_key.api.user_login_link_id');
        $expire = config('redis_key.api.user_login_expire');
        $expireTime = time() + $expire;

        $boolUserId = $redis->set($token, $uid, $expire);
        $mountKey = config('redis_key.api.user_login_mount_key').$uid;
        if (!empty($params['link_id'])) {
            $redis->del($mountKey);
            $redis->set($linkIdKey . $token, $params['link_id'], $expire);
        }
        $boolUser = $redis->set($key . $uid, json_encode($params), $expire);

        // 是否是通过挂载的小程序进来
        if (!empty($params['isMount'])) {
            // file_put_contents('./public_login.txt', date('Y-m-d H:i:s').' ：'.json_encode($params).'；'.PHP_EOL, FILE_APPEND);
            $redis->set($mountKey, 1, $expire+(15*60*60*24));
        }

        if (empty($boolUserId) || empty($boolUser)) {
            return [];
        }

        $loginRecordData = [
            'uid' => $uid,
            'date' => date('Ymd'),
            'month' => date('Ym'),
            'ip' => Common::getClientIp(),
            'created_at' => time(),
            'link_id' => ($params['link_id'] ?? ''), // add by huangxiaoshan 2023-07-18
        ];
        if ($officialAccountId) {
            $loginRecordData['official_account_id'] = $officialAccountId;
        }
        $this->userLoginRecordModel->edit($loginRecordData);

        $clickid = $params['clickid'] ?? '';
        if ($clickid && !empty($params['link_id'])) {
            $url = 'https://analytics.oceanengine.com/api/v2/conversion';
            $requestData = [
                'event_type' => 'active',
                'context' => [
                    'ad' => [
                        'callback' => $clickid
                    ]
                ],
                'timestamp' => Common::msectime()
            ];
            $requestData = json_encode($requestData);
            $result = Common::curlRequest($url, 'POST', $requestData, ['Content-Type : application/json']);

            $create_at = $updated_at = date('Y-m-d H:i:s', time());
            $ads_id = '';
            if (!empty($params['promotionid'])) {
                $ads_id = $params['promotionid'];
            } else if (!empty($params['adid'])) {
                $ads_id = $params['adid'];
            }
            // 日志数据
            $log_data = [
                'uid' => ($uid ?? 0),
                'link_id' => !empty($params['link_id']) ? $params['link_id'] : "",
                'ads_id' => $ads_id,
                'click_id' => $clickid,
                'api_result' => $result, // 巨量返回的结果.
                'api_request_params' => $requestData ? $requestData : '', // 请求巨量参数
                'mini_params' => !empty($params) ? json_encode($params) : '', // 抖音小程序发来的参数。
                'created_at' => $create_at,
                'updated_at' => $updated_at
            ];
            Db::name('douyin_regcallbak_logs')->insert($log_data);
        }
        $return = ['token' => $token, 'id' => $uid, 'status' => 0, 'expire_time' => $expireTime, 'official_account_id' => $officialAccountId, 'user_name' => '', 'avatar' => '', 'link_id' => '', 'user_mount' => false];
        if ($userInfo) {
            $return['user_name'] = $userInfo['user_name'];
            $return['avatar'] = $userInfo['avatar'];
        }
        if (!empty($params['link_id'])) {
            $return['link_id'] = $params['link_id'];
        }
        // if ($redis->get($mountKey)) {
            $return['user_mount'] = true;
        // }
        return $return;
    }

    /**
     * 预支付下单
     *
     * @dateTime 2022/11/28
     * @param pay_data_id int
     * @param video_id int
     * @return array
     * @author zhixiang0215@126.com
     */
    public function advancePaymentOrder($params, $uid)
    {
        $name = isset($params['name']) ? $params['name'] : '测试数据';
        $name = addslashes(strip_tags($name));
        $total = isset($params['total_fee']) ? $params['total_fee'] : '1';
        $number = $params['number'];
        $type = isset($params['type']) ? $params['type'] : 1;
        //订单同步抖音
        $userInfo = $this->model->getInfoById($uid, 'link_id, activation_time, official_account_id, interpolate_id, advertiser_id, applet_id ,open_id, mobile_system');
        $orderNo = Common::Snowflake();//date('YmdHis') . rand(1000, 10000);
        //判断用户如果是挂载进入就将用户link_id清空
        // $mountKey = config('redis_key.api.user_login_mount_key').$uid;
        // $redis = Cache::store('redis')->handler();
        // $mount=$redis->get($mountKey);
        // if(!empty($mount)&&$mount==1){
        //     $userInfo['link_id']='';
        // }
        $payRecordData = [
            'source' => 2,
            'uid' => $uid,
            'order_no' => $orderNo,
            'name' => $name,
            'type' => $type,
            'number' => $number,
            'total_fee' => round($total / 100, 2),
            'created_at' => time(),
            'consume_date' => date('Ymd'),
            'consume_month' => date('Ym'),
            'link_id' => !empty($userInfo['link_id']) ? $userInfo['link_id'] : null,
            'activation_time' => !empty($userInfo['activation_time']) ? $userInfo['activation_time'] : null,
            'activation_date' => !empty($userInfo['activation_time']) ? date('Ymd', strtotime($userInfo['activation_time'])) : null,
            'official_account_id' => !empty($userInfo['official_account_id']) ? $userInfo['official_account_id'] : null,
            'interpolate_id' => !empty($userInfo['interpolate_id']) ? $userInfo['interpolate_id'] : null,
            'advertiser_id' => !empty($userInfo['advertiser_id']) ? $userInfo['advertiser_id'] : null,
            'device_type' => WechatService::getDeviceType($userInfo['mobile_system'] ?? ''),
            'activity_id' => $params['activity_id'] ?? 0,
        ];

        if (!empty($params['video_id'])) {
            $payRecordData['video_id'] = $params['video_id'];
        }

        $appletIds = [
            '2'
        ];

        //if (!in_array($userInfo['applet_id'], $appletIds)) {
            $payRecordData['pay_merchant'] = PayRecord::PAY_MERCHANT_DY_GENERAL;
        //}

        $payId = $this->payRecordModel->edit($payRecordData);

        //if (!in_array($userInfo['applet_id'], $appletIds)) {
            return $this->newPreOrder([
                'applet_id' => $userInfo['applet_id'],
                'pay_id' => $payId,
                'total_amount' => $total,
                'name' => $name,
                'order_no' => $orderNo,
                'device_type' => $payRecordData['device_type'],
                'video_id' => $params['video_id'] ?? 0
            ]);
        //}

        // 生成预支付请求数据
        /*
        $url = 'https://developer.toutiao.com/api/apps/ecpay/v1/create_order';

        $appletData = $this->appletModel->getInfoById($userInfo['applet_id'], 'app_id, app_secret, salt');

        $requestData = [
            'app_id' => $appletData['app_id'],
            'out_order_no' => $orderNo,
            'total_amount' => intval($total),
            'subject' => $name,
            'body' => $name,
            'valid_time' => 86400,
            'notify_url' => 'https://' . config('app.app_host') . '/api/tiktok/callback'
        ];

        $sign = self::sign($requestData, $appletData['salt']);
        $requestData['sign'] = $sign;

        $prepayData = Common::curlRequest($url, 'POST', json_encode($requestData));

        $result = json_decode($prepayData, true);

        if (empty($result) || $result['err_no']) {
            return ['message' => $result['err_tips'] ?? '预下单错误'];
        }
        $result['data']['order_no']=$orderNo;
        $result['data']['define_pay_type']='old';
        return $result['data'];
        */
    }

    // 获取支付参数签名
    private function sign($map, $salt)
    {
        $rList = array();
        foreach ($map as $k => $v) {
            if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id")
                continue;
            $value = trim(strval($v));
            $len = strlen($value);
            if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len, $len - 1) == "\"")
                $value = substr($value, 1, $len - 1);
            $value = trim($value);
            if ($value == "" || $value == "null")
                continue;
            array_push($rList, $value);
        }
        array_push($rList, $salt);
        sort($rList, 2);
        return md5(implode('&', $rList));
    }

    /**
     * 消息通知
     * @param $parameterData
     * access_token 小程序 access_token
     * app_id       小程序id
     * tpl_id       模板id
     * open_id      openid小程序用户id
     * data         用来通知消息模板{ "key1": "value1", "key2": "value2" }
     * page         跳转链接
     * return       { "err_no": 0,"err_tips": ""}
     */
    public function notificationMessage($parameterData)
    {
        $url = 'https://developer.toutiao.com/api/apps/subscribe_notification/developer/v1/notify';
        $result = Common::curlRequest($url, 'POST', json_encode($parameterData));
        return json_decode($result, true);
    }

    /**
     * 获取小程序token
     * @param $uid 用户id
     * return token
     */
    public function getAccessToken($uid)
    {
        $redis = Cache::store('redis')->handler();

        $appletData = $this->getAppletInfo($uid);

        $token = $redis->get('douyin_token' . $appletData['id']);
        if (!empty($token)) {
            return $token;
        }
        $token_url = 'https://developer.toutiao.com/api/apps/v2/token';
        //获取抖音token
        $arr = [
            'appid' => $appletData['app_id'],
            'secret' => $appletData['app_secret'],
            'grant_type' => 'client_credential',
        ];
        $data = Common::curlRequest($token_url, 'POST', json_encode($arr));
        $data = json_decode($data, true); //获取token
        if ($data['err_no'] == 0) {
            $token = $data['data']['access_token'];
            $redis->set('douyin_token' . $appletData['id'], $token, $data['data']['expires_in']);
            return $token;
        } else {
            return 0;
        }
    }

    /**
     * 获取小程序配置信息
     * @param $uid
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getAppletInfo($uid)
    {
        $userInfo = $this->model->getInfoById($uid, 'applet_id ,open_id');

        $appletData = $this->appletModel->getInfoById($userInfo['applet_id'], 'app_id, app_secret,template_id,id');

        return $appletData;
    }

    // 支付回调
    public function payCallBack($params)
    {
        // 记录日志
        $log = fopen('./tiktok_callback.txt', 'a+');
        fwrite($log, json_encode($params) . PHP_EOL);
        fclose($log);

        if ($params['type'] != 'payment') {
            return ['err_no' => 400, 'err_tips' => 'business fail'];
        }

        $callBackOrderInfo = json_decode($params['msg'], true);
        $orderNo = $callBackOrderInfo['cp_orderno'];

        Db::startTrans();
        try {
            // 加锁
            $info = Db::name('pay_record')->field('id, number, type, uid, link_id, total_fee, status,video_id,advertiser_id,pay_type')->where(['order_no' => $orderNo])->lock(true)->find();
            // 是否支付过
            $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();

            if (empty($info) || !empty($info['status'])) {
                Db::rollback();
                return ['err_no' => 400, 'err_tips' => 'business fail'];
            }
            // 修改支付状态
            $pay_type = $this->getPayType($callBackOrderInfo['way']);
            $bool = PayRecord::update([
                'platform_order_no' => $callBackOrderInfo['order_id'],
                'wechat_order_no' => !empty($callBackOrderInfo['channel_no']) ? $callBackOrderInfo['channel_no'] : null,
                'status' => 1,
                'pay_time' => time(),
                'pay_type' => $pay_type ?: $info['pay_type']
            ],['id' => $info['id']]);

            if (!$bool) {
                Db::rollback();
                return ['err_no' => 400, 'err_tips' => 'business fail'];
            }

            $userInfo = Db::name('user')->where(['id' => $info['uid']])
                ->field('open_id,money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time,ip,ua,advertiser_id,applet_id')->find();

            if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
                $saveData = [
                    'money' => $userInfo['money'] + $info['number']
                ];
            } elseif ($info['type'] == Money::TYPE_UNLOCK_ALL) {
                $saveData = [
                    //'is_members'      => 0,
                    'updated_at' =>  time(),
                ];
            } else {
                // 会员
                $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);
                $saveData = [
                    'is_members'      => 1,
                    'expiration_time' => $expirationTime
                ];
            }

            if (!$isCharge) {
                $saveData['first_charge_date'] = time();
            }

            // 现有的余额加充值的K币或修改成会员
            Db::name('user')->where(['id' => $info['uid']])->update($saveData);

            if (!empty($userInfo['interpolate_id'])) {
                Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
            }
            if (!empty($info['link_id'])) {
                $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price')->find();
                $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
                Db::name('link')->where(['id' => $info['link_id']])->update([
                    'recharge_price' => $rechargePrice
                ]);
            }
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            flog(['error' => $e->getMessage().'_'.$e->getLine(), 'out_trade_no' => $orderNo], 'dy_pay_callback');
            return ['err_no' => 400, 'err_tips' => 'business fail'];
        }

        //订单同步抖音
        if (!empty($info['video_id'])) {
            $videos_data = Db::name('videos')->where('id', $info['video_id'])->field('id,name,cover_img')->find();
        } else {
            $videos_data = Db::name('videos')->field('id,name,cover_img')->limit(1)->orderRaw("rand() , id DESC")->find();
        }
        $douyinorderurl = 'https://developer.toutiao.com/api/apps/order/v2/push';
        $order_detail = [ //订单数据
            'order_id' => $orderNo,
            'create_time' => (int)round(microtime(true) * 1000),
            'status' => '已支付',
            'amount' => 1,
            'detail_url' => 'pages/index/index',
            'total_price' => intval(bcmul($info['total_fee'], 100, 2)),
            //'detail_url' => 'pages/playvideo/playvideo',

            'item_list' => [[
                'item_code' => (string)$videos_data['id'],
                'img' => $videos_data['cover_img'],
                'title' => $videos_data['name'],
                'sub_title' => $videos_data['name'],
                'amount' => 1,
                'price' => intval(bcmul($info['total_fee'], 100, 2))
            ]]
        ];
        $post_data = [
            'access_token' => $this->getAccessToken($info['uid']),
            'app_name' => 'douyin',
            'open_id' => $userInfo['open_id'],
            'order_detail' => json_encode($order_detail),
            'order_status' => 1,
            'order_type' => 0,
            'update_time' => round(microtime(true) * 1000), //订单信息变更时间，13 位毫秒级时间戳
        ];

        $log = fopen('./tiktok_callback.txt', 'a+');
        fwrite($log, "同步抖音订单请求参数：" . json_encode($post_data) . PHP_EOL);
        fclose($log);

        $result = Common::curlRequest($douyinorderurl, 'POST', json_encode($post_data), ['Content-Type: application/json']);

        $log = fopen('./tiktok_callback.txt', 'a+');
        fwrite($log, "同步抖音订单返回结果：" . json_encode($result) . PHP_EOL);
        fclose($log);

        // 根据回传比例回传对应平台
        if ($info['link_id']) {
            /*if (empty($userInfo['ip']) || empty($userInfo['ua'])) {
                return ['err_no' => 0, 'err_tips' => 'success'];
            }
            $redis = Cache::store('redis')->handler();
            $redis->select(6);
            $ip = $userInfo['ip'];
            $ua = $userInfo['ua'];
            $monitoringData = $redis->get(md5($ip . $ua));
            if (empty($monitoringData)) {
                return ['err_no' => 0, 'err_tips' => 'success'];
            }
            $monitoringData = json_decode($monitoringData, true);

            $info['clickid'] = $monitoringData['clickid'];

            // 交给队列处理
            $info['activation_time'] = $userInfo['activation_time'];

            $jobQueueName = 'postBack';

            Queue::push('app\job\PostBack', $info, $jobQueueName);*/
        }

        // by update huangxiaoshan 2023-07-17
        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {

            $info['clickid'] = $userInfo['clickid'];
            $info['open_id'] = $userInfo['open_id'];
            $info['applet_id'] = $userInfo['applet_id'];
            $info['activation_time'] = $userInfo['activation_time'];

            // 回传队列延时3秒后执行
            $jobQueueName = 'postBack';
            Queue::push('app\job\PostBack', $info, $jobQueueName);
        }

        return ['err_no' => 0, 'err_tips' => 'success'];
    }

    //通用支付开始
    //预支付
    public function newPreOrder($params)
    {
        $appletData = $this->appletModel->getInfoById($params['applet_id'], 'app_id, app_secret, salt, offer_id');
        //订单同步抖音
        if (!empty($params['video_id'])) {
            $videos_data = Db::name('videos')->where('id', $params['video_id'])->field('id,name,cover_img')->find();
        } else {
            $videos_data = Db::name('videos')->field('id,name,cover_img')->limit(1)->orderRaw("rand() , id DESC")->find();
        }
        $skuList = [
            'skuId' => (string)$params['pay_id'],
            'price' => (int)$params['total_amount'],
            'quantity' => 1,
            'title' => $params['name'],
            'imageList' => [
                $videos_data['cover_img']
            ],
            'type' => 401,
            'tagGroupId' => 'tag_group_7272625659888041996',
        ];

        $body = [
            'skuList' => [
                $skuList
            ],
            'outOrderNo' => $params['order_no'],
            'totalAmount' => (int)$params['total_amount'],
            'payExpireSeconds' => 86400,
            'payNotifyUrl' => 'https://' . config('app.app_host') . '/api/tiktok/newcallback',
            'orderEntrySchema' => [
                'path' => 'pages/rechargerecord/rechargerecord',
                //'params' => ''
            ],
            'limitPayWayList' => [],
            //'payScene' => $params['device_type'] == PayRecord::DEVICE_TYPE_IOS ? 'IM' : '',
        ];

        if ($params['device_type'] == PayRecord::DEVICE_TYPE_IOS) {
            $body['payScene'] = 'IM';
        }

        if (!empty($appletData['offer_id'])) {
            $body['merchantUid'] = $appletData['offer_id'];
        }

        $body_str = json_encode($body, JSON_UNESCAPED_UNICODE);
        $result = $this->getByteAuthorization($body_str, $appletData['app_id']);
        if (!empty($result['message'])) {
            return ['message' => $result['message']];
        }

        return [
            'data' => $body_str,
            'byteAuthorization' => $result['data'],
            'define_pay_type' => 'new',
            'order_no' => $params['order_no'],
        ];
    }

    //抖音通用支付回调
    public function newPayCallBack($params)
    {
        if (!isset($params['type']) || $params['type'] != 'payment') {
            return ['err_no' => 400, 'err_tips' => 'business fail'];
        }
        $orderInfo = json_decode($params['msg'], true);

        $orderNo = $orderInfo['out_order_no'] ?? '';
        if (empty($orderNo)) return ['err_no' => 400, 'err_tips' => 'business fail'];

        if ($orderInfo['status'] != 'SUCCESS') return ['err_no' => 0, 'err_tips' => 'success'];

        Db::startTrans();
        try {
            // 加锁
            $info = Db::name('pay_record')
                ->field('id, number, type, uid, link_id, total_fee, pay_type,status,video_id,advertiser_id')
                ->where(['order_no' => $orderNo])->lock(true)->find();

            if (empty($info) || !empty($info['status'])) {
                Db::rollback();
                return ['err_no' => 400, 'err_tips' => 'business fail'];
            }
            $pay_type = $this->getPayType($orderInfo['pay_channel']);
            // 修改支付状态
            $bool = PayRecord::update([
                'platform_order_no' => $orderInfo['order_id'],
                'wechat_order_no' => !empty($orderInfo['channel_pay_id']) ? $orderInfo['channel_pay_id'] : null,
                'status' => 1,
                'pay_time' => time(),
                'pay_type' => $pay_type ?: $info['pay_type']
            ],['id' => $info['id']]);

            if (!$bool) {
                Db::rollback();
                return ['err_no' => 400, 'err_tips' => 'business fail'];
            }

            // 是否支付过
            $isCharge = Db::name('pay_record')->field('id')->where(['uid' => $info['uid'], 'status' => 1])->find();

            $userInfo = Db::name('user')->where(['id' => $info['uid']])
                ->field('open_id,money, expiration_time, first_charge_date, clickid, interpolate_id, activation_time,ip,ua,advertiser_id,applet_id')->find();

            if (in_array($info['type'],[Money::TYPE_COMMON,Money::TYPE_ACTIVITY])) {
                $saveData = [
                    'money' => $userInfo['money'] + $info['number']
                ];
            } elseif ($info['type'] == Money::TYPE_UNLOCK_ALL) {
                $saveData = [
                    //'is_members'      => 0,
                    'updated_at' =>  time(),
                ];
            } else {
                // 会员
                $expirationTime = self::getMemberExpireTime($info['type'], $userInfo['expiration_time']);
                $saveData = [
                    'is_members'      => 1,
                    'expiration_time' => $expirationTime
                ];
            }

            if (!$isCharge) {
                $saveData['first_charge_date'] = time();
            }

            // 现有的余额加充值的K币或修改成会员
            Db::name('user')->where(['id' => $info['uid']])->update($saveData);

            if (!empty($userInfo['interpolate_id'])) {
                Db::name('keyword_reply')->where(['id' => $userInfo['interpolate_id']])->inc('order_amount', $info['total_fee'])->update();
            }
            if (!empty($info['link_id'])) {
                $linkData = Db::name('link')->where(['id' => $info['link_id']])->field('recharge_price')->find();
                $rechargePrice = $linkData['recharge_price'] + round($info['total_fee'], 2);
                Db::name('link')->where(['id' => $info['link_id']])->update([
                    'recharge_price' => $rechargePrice
                ]);
            }
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            flog([
                'error' => $e->getMessage() . '_' . $e->getLine()
            ],'dy_new_pay_callback2');
            return ['err_no' => 400, 'err_tips' => 'business fail'];
        }
        // by update huangxiaoshan 2023-07-17
        if ($info['link_id'] && $userInfo['clickid'] && $info['advertiser_id']) {

            $info['clickid'] = $userInfo['clickid'];
            $info['open_id'] = $userInfo['open_id'];
            $info['applet_id'] = $userInfo['applet_id'];
            $info['activation_time'] = $userInfo['activation_time'];

            // 回传队列延时3秒后执行
            $jobQueueName = 'postBack';
            Queue::push('app\job\PostBack', $info, $jobQueueName);
        }
        // end
        return ['err_no' => 0, 'err_tips' => 'success'];
    }

    private function getPayType($type): ?int
    {
        if (empty($type)) return null;
        return $type == 10 ? 3 : $type;
    }

    private function getByteAuthorization($data, $appId)
    {
        // 请求时间戳
        $timestamp = time();
        // 随机字符串
        $nonceStr = $this->randStr(10);

        $privateConf = config('tiktok.prod_private_keys');
        $config = $privateConf[$appId] ?? [];
        if (empty($config)) {
            return ['message' => "private config error"];
        }
        // 应用公钥版本,每次重新上传公钥后需要更新,可通过「开发管理-开发设置-密钥设置」处获取
        $keyVersion = $config['version'];
        // 应用私钥,用于加签 重要：1.测试时请修改为开发者自行生成的私钥;2.请勿将示例密钥用于生产环境;3.建议开发者不要将私钥文本写在代码中
        $privateKeyStr = $config['key'];
        // 读取私钥
        $privateKey = openssl_pkey_get_private($privateKeyStr);
        if (!$privateKey) {
            return ['message' => "invalid private key"];
        }

        // 生成签名
        $signature = $this->getSignature("POST", "/requestOrder", $timestamp, $nonceStr, $data, $privateKey);
        if ($signature === false) {
            return ['message' => "sign error"];
        }

        // 构造 byteAuthorization
        $byteAuthorization = sprintf("SHA256-RSA2048 appid=%s,nonce_str=%s,timestamp=%s,key_version=%s,signature=%s", $appId, $nonceStr, $timestamp, $keyVersion, $signature);
        return [
            'message' => '',
            'data' => $byteAuthorization
        ];
    }

    private function getSignature($method, $url, $timestamp, $nonce, $data, $privateKey) {
        $targetStr = $method. "\n" . $url. "\n" . $timestamp. "\n" . $nonce. "\n" . $data. "\n";
        openssl_sign($targetStr, $sign, $privateKey, OPENSSL_ALGO_SHA256);
        $sign = base64_encode($sign);
        return $sign;
    }

    private function randStr($length = 8) {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[mt_rand(0, strlen($chars) - 1)];
        }
        return $str;
    }

    // 获取会员到期时间
    public function getMemberExpireTime($type, $timestamp = 0)
    {
        if ($timestamp && $timestamp > time()) {
            // 大于当前时间表示续费
            switch ($type) {
                case 2: // 年卡会员
                    $expirationTime = strtotime('+1day', strtotime('+12' . ' month', $timestamp));
                    break;
                case 3: // 半年会员
                    $expirationTime = $timestamp + 86400 * 180;
                    break;
                case 4: // 季卡会员
                    $expirationTime = $timestamp + 86400 * 90;
                    break;
                case 5: // 60天会员
                    $expirationTime = $timestamp + 86400 * 60;
                    break;
                case 6: // 30天会员
                    $expirationTime = $timestamp + 86400 * 30;
                    break;
                case 7: // 10天会员
                    $expirationTime = $timestamp + 86400 * 10;
                    break;
                case 8: // 7天会员
                    $expirationTime = $timestamp + 86400 * 7;
                    break;
                case 11: // 3天会员
                    $expirationTime = $timestamp + 86400 * 3;
                    break;
                case 15: // 1天会员
                    $expirationTime = $timestamp + 86400 * 1;
                    break;
                default:
                    $expirationTime = time();
                    break;
            }
        } else {
            // 开通会员或会员已过期
            switch ($type) {
                case 2: // 年卡会员
                    $expirationTime = strtotime(date('Y-m-d', strtotime('+1day', strtotime(date('Y-m-d', strtotime('+12' . ' month'))))));
                    break;
                case 3: // 半年会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 181;
                    break;
                case 4: // 季卡会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 91;
                    break;
                case 5: // 60天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 61;
                    break;
                case 6: // 30天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 31;
                    break;
                case 7: // 10天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 11;
                    break;
                case 8: // 7天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 8;
                    break;
                case 11: // 3天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 4;
                    break;
                case 15: // 1天会员
                    $expirationTime = strtotime(date('Y-m-d')) + 86400 * 2;
                    break;
                default:
                    $expirationTime = time();
                    break;
            }
        }
        return $expirationTime;
    }
}
